-- AutoPatch
-- create by zouyb
-- 自动更新模块

-- 代码的搜索目录
cc.FileUtils:getInstance():addSearchPath("assets/src");
cc.FileUtils:getInstance():addSearchPath("assets/res");

cc.FileUtils:getInstance():addSearchPath("src");
cc.FileUtils:getInstance():addSearchPath("res");

-- 关闭文件找不到的log
cc.FileUtils:getInstance():setPopupNotify(false);

collectgarbage("collect");
collectgarbage("setpause", 100);
collectgarbage("setstepmul", 5000);

require "cocos/init"
require "core/base/Base"
require "core/base/String"
require "core/base/Log"
require "core/base/Debug"
require "core/base/XmlHttpRequest"
require "core/base/UserDefaultM"
require "core/base/Table"
require "core/base/DaemonData"
require "core/mobile/Platform"
require "core/mobile/NetworkStatus"
require "core/mobile/Language"
require "core/loc/localization"
require "core/module/EventMgr"
require "Config"
require "core/autopatch/Base"
require "core/autopatch/Diagnosis"

CLIENT_VERSION = "1.0"

-- 自动更新页面
local ui;

-- 本地manifest的路径
local sceneManifests    = "manifest/project.manifest";
local versionManifests  = "manifest/version.manifest";

-- 下载资源保存的路径
local storagePaths = "update_res/";

-- "更新中"提示的坐标
local UPDATEING_LABEL_X = -2;
local UPDATEING_LABEL_Y = -160;

-- 更新结果
AP_UPDATE_OK            = 1000;  -- 更新成功
AP_MAINTAINING          = 1001;  -- 维护中
AP_NO_DISLIST_FILE      = 1002;  -- 下载dislist失败
AP_NO_MANIFEST          = 1003;  -- 找不到本地manifest
AP_PARSE_MANIFEST_ERROR = 1004;  -- 解析manifest失败
AP_FILE_DOWNLOAD_ERROR  = 1005;  -- 文件下载失败
AP_NO_ENOUGH_STORAGE    = 1006;  -- 磁盘空间不足

-- 进度条光效偏移
local offsetX = 0;
local offsetY = 25;

-- 草地偏移
local grassOffset = ({18, 40, 60, 74, 82});

-- 长花的概率
local flowerProb = 50;

-- 内部函数声明
local compareVersion;
local getLocImg;
local processDic;

local frameWidth = cc.Director:getInstance():getOpenGLView():getFrameSize().width;
local frameHeight = cc.Director:getInstance():getOpenGLView():getFrameSize().height;

-- 公告内容，nil（尚未获取或没有公告） 字符串（公告内容）
_G["outBoard"] = nil;
local gotBoard = false;

-- 区组列表，nil（尚未获取） table（具体内容）
_G["dislist"] = nil;
local gotDis = false;

-- 网络测试是否结束
local gotNetwork = false;

-- 获取本地化图片资源
function getLocImg(filename)
    local locImg;
    local lang = UserDefaultM.getStringForKey("APP_LANG");
    if lang == "zh" then
        locImg = "images/ui/loc/";
    else
        locImg = string.format("images/ui/loc_%s/", lang);
    end

    if nil == filename then
        return locImg .. "default.png";
    end
    local fullName = locImg..filename..".png";

    -- TODO ios版本显示临时处理下
    if isIosPlatform() then
        if filename == "completed" then
            fullName = locImg .. "ios_completed" .. ".png";
        end
    end

    if cc.FileUtils:getInstance():isFileExist(fullName) then
        return fullName;
    else
        return locImg .. "default.png";
    end
end

-- UIAutoPatch继承自Layer
UIAutoPatch = class("UIAutoPatch", function()
    return cc.Layer:create();
end);

function UIAutoPatch.create()
    return UIAutoPatch.new();
end

-- 构造函数
function UIAutoPatch:ctor()
    ui = self;

    -- 加载合图
    cc.SpriteFrameCache:getInstance():addSpriteFrames("images/ui/login_effect.plist");

    -- 注册事件处理回调函数
    self:registerEventCallback();

    -- 初始化sdk
    self:initSdk();
end

-- 初始化sdk
function UIAutoPatch:initSdk()
    -- 初始化sdk
    if isAndroidPlatform() then
        require "src/game/sdk/android/SdkMgr";
        SdkMgr.init();
    elseif isIosPlatform() then
        require "src/game/sdk/ios/SdkMgr";
        SdkMgr.init();
    else
        require "game/sdk/SdkMacros"
        require "game/sdk/SdkAgent"
        require "game/sdk/PurchaseToolkit"
    end
end

-- 注册事件处理回调函数
function UIAutoPatch:registerEventCallback()
    -- 注册渠道sdk初始化完毕的回调
    EventMgr.removeAll("UIAutoPatch");
    EventMgr.register("UIAutoPatch", event.CHANNEL_SDK_INITED, function(platform)
        --self:showQcLogo();
    end);

    local function onNodeEvent(event)
        if "enter" == event then
            if _G["hasShowBoard"] or _G["hasShowUserProtocol"] then
                processDic();
                self:init();
            else
                -- 显示LOGO
                self:showLogo();
            end
        elseif "exit" == event then
            if self.am then
                self.am:release();
            end

            if self.listener then
                print("removeEventListener");
                cc.Director:getInstance():getEventDispatcher():removeEventListener(self.listener);
            end

            -- cclog("UIAutoPatch 析构清理")
            EventMgr.removeAll("UIAutoPatch");
        end
    end

    self:registerScriptHandler(onNodeEvent);
end

-- 注册back键处理
function UIAutoPatch:registerBackEventHandler(node)
    local function onKeyReleased(keyCode, event)
        if keyCode == cc.KeyCode.KEY_BACK then
            self:handleBackEvent(node);
        end
    end

    local listener = cc.EventListenerKeyboard:create()
    listener:registerScriptHandler(onKeyReleased, cc.Handler.EVENT_KEYBOARD_RELEASED )

    local eventDispatcher = node:getEventDispatcher()
    eventDispatcher:addEventListenerWithSceneGraphPriority(listener, node);
end

-- 处理back键
function UIAutoPatch:handleBackEvent(node)
    if self.tickNode == nil then
        self.tickNode = cc.Node:create();
        node:addChild(self.tickNode);
    end

    if not node.exitFlag then
        node.exitFlag = true;
        print("press again to exit");
        if isAndroidPlatform() then
            callJavaFunc("showToast", { getLocStr("press_again_to_exit") }, "(Ljava/lang/String;)V");
        end
    else
        print("exit");
        if isAndroidPlatform() then
            callJavaFunc("exitSystem", {}, "()V");
        end
    end
    self.tickNode:stopAllActions();
    performWithDelay(self.tickNode, function() node.exitFlag = false; end, 2);
end

-- 先显示LOGO
function UIAutoPatch:showLogo()
    printG("通知启动后台的一些初始加载");
    self:runTask();

    printG("显示LOGO");
    if isAndroidPlatform() then
        -- 获取应用名
        local bundleId = getBundleId();
        if string.endWith(bundleId, ".uc") then
            -- uc渠道要等uc的logo显示完后再显示qc的logo
            performWithDelay(self, self.showQcLogo, 1.0);
        else
            self:showQcLogo();
        end

    else
        self:showQcLogo();
    end

    EventMgr.fire(event.AUTOPATCH_SHOW_LOGO);
end

-- 显示青瓷LOGO
function UIAutoPatch:showQcLogo()
    printG("显示QCLOGO")

    local w = frameWidth;
    local h = frameHeight;

    -- 白色背景
    local layout = ccui.Layout:create();
    layout:setBackGroundColorType(ccui.LayoutBackGroundColorType.solid);
    layout:setBackGroundColor(cc.c3b(255, 255, 255));
    layout:setContentSize(cc.size(w, h));
    self:addChild(layout);
    self.logoLayout = layout;

    -- 公司图案
    local imageView = ccui.ImageView:create();
    local iconPath  = "images/logo/qcplay.png";

    -- 雷霆的版本需要显示雷霆LOGO
    if PUBLISH_PLATFORM == PUBLISH_TYPE_LT then
        iconPath = "images/logo/qcplay_lt.png";
    end

    imageView:loadTexture(iconPath);
    imageView:setPosition(w / 2, h / 2);
    imageView:setScale(w / 640);
    self:addChild(imageView);
    self.logoImageView = imageView;

    -- 记录下开始显示青瓷LOGO的时间
    self.logoTime = os.time();

    if PUBLISH_PLATFORM == PUBLISH_TYPE_LT then
        self:checkOutBoard();
    elseif PUBLISH_PLATFORM == PUBLISH_TYPE_UC or
       (UserDefaultM.getStringForKey("isForBh") ~= "0" and not isOverseaPackage()) then
        -- 版署版本显示健康游戏忠告
        self:showBhTips();
    else
        -- 检查重大公告/阻塞在这里等待
        self:checkOutBoard();
    end
end

-- 显示健康游戏忠告
function UIAutoPatch:showBhTips()
    printG("显示健康游戏忠告")
    local function startInit()
        local w = frameWidth;
        local h = frameHeight;

        -- 健康游戏忠告
        local imageView = self.logoImageView;
        imageView:loadTexture("images/logo/bh.png");
        imageView:setPosition(w / 2, h / 2);
        imageView:setScale(w / 640);

        -- 检查重大公告/阻塞在这里等待
        self.logoTime = os.time();
        self:checkOutBoard();
    end

    local timeout = 5;
    if DEBUG_MODE == 1 or DEBUG_MODE == true then timeout = 2 end
    performWithDelay(self, startInit, timeout);
end

-- 检查更新前公告
function UIAutoPatch:checkOutBoard()
    printG("检查更新前公告")
    local timeout = 5;
    if DEBUG_MODE == 1 or DEBUG_MODE == true then timeout = 2 end
    local f;
    f = function()
        if os.time() - self.logoTime <= timeout then
            -- 显示时间未到，继续延迟等待
            print("显示时间未到，继续延迟等待")
            performWithDelay(self, f, 0.5);
            return;
        end

        local tempGotNetwork = true;
        if PUBLISH_PLATFORM == PUBLISH_TYPE_LT and isIosPlatform() then
            tempGotNetwork = gotNetwork;
        end

        if false == gotBoard or false == gotDis or false == tempGotNetwork then
            -- 公告和区组列表尚未获取完毕，继续延迟等待
            print("公告和区组列表尚未获取完毕，继续延迟等待")
            performWithDelay(self, f, 0.5);
            return;
        end

        if nil == _G["outBoard"] then
            -- 不显示公告
            if self.logoLayout then
                self:removeChild(self.logoLayout);
            end
            if self.logoImageView then
                self:removeChild(self.logoImageView);
            end

            self:init();
            return;
        end

        -- 需要显示公告
        self:gotoMain();
    end

    performWithDelay(self, f, 0.5);
end

-- 界面初始化
function UIAutoPatch:init()
    -- 初始化语言环境
    self:initLanguage();

    -- 检查网络状态
    local wifiConnected = NetworkStatus.isWifiConnected();
    local mobileConnected = NetworkStatus.isMobileConnected();
    printG("网络wifi状态:"..tostring(wifiConnected));
    printG("网络mobile状态:"..tostring(mobileConnected));

    printG("MAC地址:".. tostring(NetworkStatus.getMacAddress()));

    self.wifiConnected = wifiConnected;
    self.mobileConnected = mobileConnected;

    local w = frameWidth;
    local h = frameHeight;

    -- 自动更新图片背景
    local imageView = ccui.ImageView:create();

    if UserDefaultM.getStringForKey("APP_LANG") == "tw" then
        -- 繁体中文版本需要加载特殊的背景图
        imageView:loadTexture("images/ui/login/bg_tw.jpg");
    else
        imageView:loadTexture("images/ui/login/bg.jpg");
    end
    local scaleCo = math.max(w / 768, h / 1136);
    imageView:setScale(scaleCo);

    imageView:setPosition(w/2, h/2);
    self:addChild(imageView);

    local CT = ccui.Widget:create();
    CT:setPosition(w/2, h/4);
    self:addChild(CT);
    self.CT = CT;

    -- 下载进度条背景
    local loadingBar1BG = ccui.ImageView:create();
    loadingBar1BG:loadTexture("images/ui/login/effect/progress_bg.png");
    loadingBar1BG:setPosition(0, -120);
    loadingBar1BG:setAnchorPoint(0.5, 0);
    CT:addChild(loadingBar1BG);
    self.loadingBar1BG = loadingBar1BG;

    -- 进度条动画
    local pNode = cc.Node:create();
    local barEffect = ccui.ImageView:create();
    barEffect:loadTexture("images/ui/login/effect/progress_gb.png");
    barEffect:setAnchorPoint(cc.p(0.5, 0));
    self.barEffect = barEffect;
    pNode:addChild(barEffect);
    self.pNode = pNode;
    self.pNode:setPosition(0, loadingBar1BG:getPositionY() + 25);
    CT:addChild(self.pNode);
    self.pNode:setVisible(false);

    -- "更新中"提示语
    local spUpdateTip = ccui.Text:create();
    spUpdateTip:setPosition(UPDATEING_LABEL_X, UPDATEING_LABEL_Y);
    CT:addChild(spUpdateTip);
    self.spUpdateTip = spUpdateTip;
    self.spUpdateTip:setVisible(false);

    -- 状态条
    local statusLabel = ccui.Text:create();
    statusLabel:setPosition(0, loadingBar1BG:getPositionY());
    CT:addChild(statusLabel);
    self.statusLabel = statusLabel;

    local function onDectect(sender, eventType)
        if eventType == ccui.TouchEventType.began then
            local function start()
                print("开始网络检测");
                self.tryNetworkFix();
            end
            performWithDelay(sender, start, 5);
        elseif eventType == ccui.TouchEventType.ended
            or eventType == ccui.TouchEventType.canceled then
            sender:stopAllActions();
        end
    end

    -- 网络检测按钮
    local detectLayer = ccui.Layout:create();
    detectLayer:setContentSize(cc.size(200, 200));
    local btnPosX = 320 - 200;
    local btnPosY = h * 3 / 4 / (w/640) - 200;
    detectLayer:setTouchEnabled(true);
    detectLayer:setPosition(btnPosX, btnPosY);
    detectLayer:addTouchEventListener(onDectect);
    CT:addChild(detectLayer);

    local diagnosisLabel = ccui.Text:create();
    diagnosisLabel:setPosition(0,  h * 3 / 4 / (w/640) - 100);
    self:setTextStyle(diagnosisLabel);
    diagnosisLabel:setString(getCoreLocStr("btn_text_detecting"));
    diagnosisLabel:setFontSize(20);
    diagnosisLabel:getVirtualRenderer():setHorizontalAlignment(cc.TEXT_ALIGNMENT_CENTER);
    CT:addChild(diagnosisLabel);
    diagnosisLabel:setVisible(false);
    self.diagnosisLabel = diagnosisLabel;

    -- 日志信息
    if SHOW_AUTO_PATCH_LOG then
        local logLabel = ccui.Text:create();
        logLabel:setFontSize(22);
        logLabel:setAnchorPoint(cc.p(0, 0.5));
        logLabel:enableOutline(cc.c4b(0x10, 0x10, 0x10, 0xff), 2);
        logLabel:setPosition(loadingBar1BG:getPositionX() - loadingBar1BG:getContentSize().width / 2, -200);
        CT:addChild(logLabel);
        self.logLabel = logLabel;
    end
    CT:setScale(w / 640);

    -- 开始启动自动更新
    self:checkVersion();
end

-- 显示本地版本号
function UIAutoPatch:showLocalVersion()
    local w = frameWidth;
    local h = frameHeight;

    -- 本地版本号
    local versionCfg = readVersionManifest();
    local cfgVersion = versionCfg["version"] or "";

    if DEBUG_MODE == 1 then
        cfgVersion = cfgVersion;
    else
        local list = string.explode(cfgVersion, "-");
        if #list == 1 then
            cfgVersion = list[1];
        elseif #list == 2 then
            cfgVersion = list[2];
        end
    end

    -- 版本号
    if self.showVersionLabel == 1 then
        local VersionLabel = ccui.Text:create();
        VersionLabel:setFontSize(26);
        VersionLabel:setString("ver:"..cfgVersion);
        VersionLabel:setAnchorPoint(cc.p(1, 0.5));
        -- 位置和UILogin显示的version一致
        VersionLabel:setPositionX(610 * w / 640);
        VersionLabel:setPositionY(938 * h / 960);
        VersionLabel:setScale(w / 640);
        self:addChild(VersionLabel);
    end
end

-- 执行自动更新
function UIAutoPatch:doAutoUpdate()
    -- 自动更新开始
    EventMgr.fire(event.AUTOPATCH_START);

    -- 如果是进行资源包测试，则直接跳转
    if true == TEST_PKG_DOWNLOAD or 1 == TEST_PKG_DOWNLOAD or self.testPkgDownload == 1 then
        self:gotoMain();
        return;
    end
    -- 初始化下载管理器
    if self.targetParts == nil then
        self.targetParts = "assets|plus";
    end

    if self.am == nil then
        -- print("target parts:" .. self.targetParts);
        local am = cc.AssetsManagerEx:create(sceneManifests, cc.FileUtils:getInstance():getWritablePath() .. storagePaths, self.targetParts);
        -- print(cc.FileUtils:getInstance():getWritablePath() .. storagePaths);
        am:retain();
        self.am = am;

        -- wifi不通，移动网络通，需要玩家确认下载
        if not self.wifiConnected and self.mobileConnected then
            am:setDownloadConfirmFlag(true);
        end
    end

    local function showlog(msg)
        print(msg);

        if SHOW_AUTO_PATCH_LOG then
            self.logLabel:setString(msg);
        end
    end

    local am = self.am;
    if self.dowloadRootUrl and self.dowloadRootUrl ~= "" then
        showlog("下载URL:" .. self.dowloadRootUrl);

        am:setDownloadRootUrl(self.dowloadRootUrl);

        -- 如果服务器正在灰度升级， 需要根据服务器支持版本号来下载更新资源
        if _G["dislist"]["upgrade_by_server_version"] == 1 then
            local targetVersion = UserDefaultM.getStringForKey("server_support_ver");
            if targetVersion ~= nil and targetVersion ~= "" and targetVersion ~= "0" then
                -- 只要版本号不一致就更新，相当于可以支持回退版本
                -- (一般是要版本号大于本地版本才更新)
                am:setDownloadWhenMismatch();

                -- 重新设置目标下载URL
                local dowloadRootUrl = string.gsub(self.dowloadRootUrl, "latest",  targetVersion);
                showlog("重新设置目标下载URL:" .. dowloadRootUrl);
                am:setDownloadRootUrl(dowloadRootUrl);
                self.dowloadRootUrl = dowloadRootUrl;
            end
        end
    end

    -- 播放序列帧动画
    self:progressBarEffect();

    if not am:getLocalManifest():isLoaded() then
        -- 更新失败，跳过
        printR("Fail to update assets, step skipped.");
        self:setUpdateResult(AP_NO_MANIFEST);
        self:updateUpdatingLabel(false);
        self:updateStatus("completed");
        self:finish();
    else
        local function onUpdateEvent(event)
            local eventCode = event:getEventCode();
            -- print("eventCode:"..eventCode);
            if eventCode == cc.EventAssetsManagerEx.EventCode.ERROR_NO_LOCAL_MANIFEST then
                -- 无本地Mainifest文件，跳过
                showlog("No local manifest file found, skip assets update.");
                self:setUpdateResult(AP_NO_MANIFEST);
                self:updateUpdatingLabel(false);
                self:updateStatus("completed");
                self:finish();
            elseif eventCode == cc.EventAssetsManagerEx.EventCode.NEED_CONFIRM then
                local totalSize = event:getTotalSize();
                if totalSize > 0 then
                    -- 显示下载确认框
                    self:showDownloadConfirm(totalSize);
                end
            elseif eventCode == cc.EventAssetsManagerEx.EventCode.UPDATE_PROGRESSION then
                -- 单个文件更新进度
                local assetId = event:getAssetId();
                showlog("UPDATE_PROGRESSION: assetId = " .. tostring(assetId));
                if assetId == "@manifest" or assetId == "@version" then
                    self:updateStatus("checking");
                else
                    local percent = event:getPercent();
                    local totalSize = event:getTotalSize();

                    local percent = event:getPercent();

                    self:playUpdateAnimation(percent);

                    -- 显示已下载和总下载数据大小
                    if totalSize ~= 0 then
                        self:updateUpdatingLabel(true);
                        local msg = string.format("%.2fM/%.2fM", totalSize*percent/100/1024/1024, totalSize/1024/1024);
                        self:updateDownloadInfo(msg);

                        local printStr = string.format("assetId:%s update finished, percent:%d, %s", assetId, percent, string.gsub(msg, ':', 'M'));
                        showlog(printStr);
                    end
                end
            elseif eventCode == cc.EventAssetsManagerEx.EventCode.ASSET_UPDATED then
                -- 单个文件更新成功
            elseif eventCode == cc.EventAssetsManagerEx.EventCode.ERROR_DOWNLOAD_MANIFEST or
                eventCode == cc.EventAssetsManagerEx.EventCode.ERROR_PARSE_MANIFEST then
                -- 下载或解析Mainifest出错，跳过
                showlog("Fail to download manifest file, update skipped.");
                self:setUpdateResult(AP_PARSE_MANIFEST_ERROR);
                self:updateUpdatingLabel(false);
                self:updateStatus("completed");
                self:finish();

                local errorMsg = "MSG: "..event:getMessage()..", URL: "..tostring(self.dowloadRootUrl);
                if DOWNLOAD_ERROR_REPORT_URL then
                    local errorMsg = string.format("%s %s", os.date(), tostring(errorMsg));
                    postDataToRemoteHost(DOWNLOAD_ERROR_REPORT_URL, errorMsg);
                end
            elseif eventCode == cc.EventAssetsManagerEx.EventCode.ALREADY_UP_TO_DATE then
                self:setUpdateResult(AP_UPDATE_OK);
                self:updateStatus("updated");
                self:updateUpdatingLabel(false);
                self:finish();
            elseif eventCode == cc.EventAssetsManagerEx.EventCode.UPDATE_FINISHED then
                self:setUpdateResult(AP_UPDATE_OK);

                --在线更新成功
                showlog("Update finished.");
                self:updateUpdatingLabel(false);
                self:updateStatus("completed");
                self:finish();
            elseif eventCode == cc.EventAssetsManagerEx.EventCode.ERROR_UPDATING then
                local errorMsg = "ASSET: " .. event:getAssetId() .. ", ERROR: " .. event:getMessage();
                showlog(errorMsg);

                -- 更新出错，报错
                self:setUpdateResult(AP_FILE_DOWNLOAD_ERROR);

                if DOWNLOAD_ERROR_REPORT_URL then
                    local errorMsg = string.format("%s %s", os.date(), tostring(errorMsg));
                    postDataToRemoteHost(DOWNLOAD_ERROR_REPORT_URL, errorMsg);
                end
            elseif eventCode == cc.EventAssetsManagerEx.EventCode.ERROR_DECOMPRESS
                or eventCode == cc.EventAssetsManagerEx.EventCode.NO_ENOUGH_STORAGE then
                -- 磁盘空间不足
                local errorMsg = "no enough storage";
                showlog(errorMsg);
                self:setUpdateResult(AP_NO_ENOUGH_STORAGE);
                self:updateUpdatingLabel(false);
                self:updateStatus("completed");
                self:finish();
                if DOWNLOAD_ERROR_REPORT_URL then
                    postDataToRemoteHost(DOWNLOAD_ERROR_REPORT_URL, errorMsg);
                end
            elseif eventCode == cc.EventAssetsManagerEx.EventCode.UPDATE_FAILED then
                -- 此处无需再调用setUpdateResult， 如果有错误，前面已经会设置具体的错误码

                showlog("Update failed.");
                if self and self.finish then
                    self:updateUpdatingLabel(false);
                    self:updateStatus("completed");
                    self:finish();
                end
            end
        end
        self.listener = cc.EventListenerAssetsManagerEx:create(am,onUpdateEvent);
        cc.Director:getInstance():getEventDispatcher():addEventListenerWithFixedPriority(self.listener, 1);

        am:update();
    end
end

-- 尝试网络修复
function UIAutoPatch:tryNetworkFix()
    -- 显示网络检测中
    self.diagnosisLabel:setVisible(true);

    local function onFinish(ret)
        --  隐藏网络检测中
        self.diagnosisLabel:setVisible(false);

        -- 标记已经尝试修复过了
        DIAGNOSE_FIX = true;

        local function onOK()
            if ret then
                -- 重启自动更新
                enterAutoPatchScene();
            else

                -- 显示原来的错误
                self:showResetAppConfirm();
            end
        end

        local msg = string.format(getCoreLocStr("detect_hint"), getDiagnoseID());
        nativeAlert(getCoreLocStr("system_prompt"), msg, getCoreLocStr("btn_text_ok"), onOK);
    end

    startDiagnose(onFinish);
end

-- 显示自动更新失败提示框
function UIAutoPatch:showResetAppConfirm()
    local function okFunc()
        if isAndroidPlatform() then
            -- 重启应用
            restartApp();
            return;
        end

        -- 重新加载APP，只在AutoPatch阶段可以这么做
        self:unregisterScriptHandler();
        if Communicate and Communicate.reset then
            Communicate.reset();
        end

        cc.ScriptEngineManager:getInstance():reset();
    end

    if PUBLISH_PLATFORM == PUBLISH_TYPE_LT and isIosPlatform() then
        -- 雷霆ios版本，处理下IOS10无法登陆问题
        if self.networkDisabled then
            -- 如果是网络开关未打开, 提示玩家
            local title = getCoreLocStr("system_prompt");
            local content = getCoreLocStr("network_fail");
            local textFix = getCoreLocStr("fix_problem");
            local textRetry = getCoreLocStr("btn_text_retry");
            local function fixFunc()
                -- 跳转到解决方案链接
                openUrl("http://gumballs.leiting.com/jump.php");

                -- 关闭游戏
                exitApp();
            end
            nativeConfirm(title, content, textRetry, textFix, okFunc, fixFunc);
            return;
        end
    end

    local title = getCoreLocStr("update_fail_title");
    local msg = string.format(getCoreLocStr("update_fail_message"), self.resultCode);
    local btnText = getCoreLocStr("btn_text_retry");

    local feedBackText = getCoreLocStr("response_problem");
    if not DIAGNOSE_FIX then
        feedBackText = getCoreLocStr("btn_text_fix");
    end

    local function feedbackFunc()
        if not DIAGNOSE_FIX then
            -- 先尝试修复下
            self:tryNetworkFix();
            return;
        end

        nativeConfirm(title, msg, btnText, feedBackText, okFunc, feedbackFunc);
        showWebFAQs();
    end

    nativeConfirm(title, msg, btnText, feedBackText, okFunc, feedbackFunc);
end

-- 显示下载确认框
function UIAutoPatch:showDownloadConfirm(totalSize)
    local function okFunc()
        -- 确认后继续下载
        self.am:update();
    end
    local function cancelFunc()
        -- 直接退出游戏
        exitApp();
    end

    local title = getCoreLocStr("package_download_title");
    local msg = string.format(getCoreLocStr("non_wifi_environment"), totalSize/1024/1024);
    nativeConfirm(title, msg, getCoreLocStr("btn_text_ok"), getCoreLocStr("btn_text_cancel"), okFunc, cancelFunc);
end

-- 初始化语言环境
function UIAutoPatch:initLanguage()
    -- 首次登陆时，根据系统语言设置app语言版本
    local lang = UserDefaultM.getStringForKey("APP_LANG");
    print("lang:" .. tostring(lang));
    if lang == nil or lang == "" then
        -- 取系统默认语言/如果取不到会返回包中Config的默认语言
        local sysLang = Language.getSysLang();
        print("sysLang:" .. tostring(sysLang));

        -- 需要独立下载的语言包
        local langPkg = getLangPkg(sysLang);
        if langPkg ~= nil then
            self.lang = sysLang;
            UserDefaultM.setStringForKey("APP_LANG", sysLang);
            -- 设置独立包开启标志
            cc.UserDefault:getInstance():setStringForKey(langPkg, "1");

            -- 设置语言包需要下载标记
            cc.UserDefault:getInstance():setStringForKey("need_download_lang_pkg", "1");
        elseif sysLang == "zh" or sysLang == "en" then
            self.lang = sysLang;
            UserDefaultM.setStringForKey("APP_LANG", sysLang);
        else
            self.lang = DEFAULT_LANGUAGE or "en";
            UserDefaultM.setStringForKey("APP_LANG", self.lang);
        end
    else
        self.lang = lang;
    end

    print("self.lang:" .. tostring(self.lang));
    -- 初始化本地化资源
    initCoreLang();
end

-- 检查版本号
function UIAutoPatch:checkVersion()
    if nil == _G["dislist"] then
        _G["dislist"] = nil;
        self:setUpdateResult(AP_NO_DISLIST_FILE);
        self:updateStatus("completed");
        self:finish();
        return;
    end

    local r = auCheckVersion();
    if r == "maintaining" then
        -- 维护中，直接认为更新完成
        self:setUpdateResult(AP_MAINTAINING);
        self:updateStatus("completed");
        self:finish();
    elseif r == "=" then
        -- 版本号一致，无须做任何更新
        -- FIXED: 但目前不是这样子的，需要看下为什么
        self:doAutoUpdate();
    elseif r == true then
        self:doAutoUpdate();
    elseif r == false then
        -- 需要重新下载
        local url = getPackageDownloadUrl();
        printG("当前大版本号发生变化，需要重新下载安装包。url = " .. url);
        if url == "" then
            -- 获取不到下载链接，直接执行自动更新
            self:doAutoUpdate();
            return;
        end

        local title = getCoreLocStr("package_download_title");
        local btnText = getCoreLocStr("btn_text_package_download");
        local function onOk()
            openUrl(url);
            exitApp();
        end

        if isAndroidPlatform() then
            self.pNode:setVisible(false);

            local content = getCoreLocStr("package_download_tip");
            if PUBLISH_PLATFORM == PUBLISH_TYPE_GOOGLE_PLAY then
                -- GooglePlay平台
                content = getCoreLocStr("package_google_download_tip");
            end

            nativeAlert(title, content, btnText, onOk);
        elseif isIosPlatform() then
            local msg = getCoreLocStr("package_appstore_download_tip");
            nativeAlert(title, msg, btnText, onOk);
        else
            -- 其他平台暂不支持
            -- TODO 默认走自动更新流程
            self:doAutoUpdate();
        end
    end
end

-- 设置更新结果
function UIAutoPatch:setUpdateResult(resultCode)
    self.resultCode = resultCode;
    EventMgr.fire(event.AUTOPATCH_RESULT, { ["resultCode"] = resultCode, });
end

-- 更新完毕，跳转到Main
function UIAutoPatch:finish(status)
    self.pNode:setVisible(false);

    -- 更新完毕的动画
    self:finishEffect();

    if self.am then
        CLIENT_VERSION = self.am:getLocalManifest():getVersion();
    else
        self:initLocalVersion();
    end

    if (CLIENT_VERSION == nil or CLIENT_VERSION == "") then
        CLIENT_VERSION = "1.0";
    end

    if self.resultCode == AP_UPDATE_OK then
        cc.UserDefault:getInstance():setStringForKey("need_download_lang_pkg", "0");
    end

    EventMgr.fire(event.AUTOPATCH_FINISH, { ["resultCode"] = resultCode, });

    printG("自动更新结果："..tostring(self.resultCode));
    if self.resultCode == AP_UPDATE_OK
        or self.resultCode == AP_MAINTAINING or isWindowsPlatform() then
        -- 更新成功，或者维护中，跳转到游戏界面
        -- windows下也直接跳转
        self:gotoMain();
        return;
    end

    -- 更新失败了，弹出本地错误提示框，重新启动或退出游戏
    self:showResetAppConfirm();
end

-- 设置文本样式
function UIAutoPatch:setTextStyle(textNode)
    local outlineColor = cc.c4b(0x20, 0x20, 0x20, 0xff);
    local outlineSize = 2;
    local textColor = cc.c4b(0xff, 0xff, 0xff, 0xff);

    local locFontFile = getCoreLocFontName(self.lang);
    if locFontFile ~= nil and cc.FileUtils:getInstance():isFileExist(locFontFile) then
        textNode:setTTFConfig(locFontFile, 25, outlineColor,  outlineSize);
    else
        textNode:setFontSize(25);
        textNode:enableOutline(outlineColor, outlineSize);
    end

    textNode:setColor(cc.c4b(0xff, 0xff, 0xff, 0xff));
    textNode:setTextColor(textColor);
end

-- 更新状态
function UIAutoPatch:updateStatus(status)
    self.statusLabel:setPositionX(0);
    self.statusLabel:setVisible(true);
    self:setTextStyle(self.statusLabel);
    self.statusLabel:setString(getCoreLocStr("status_"..status));
end

-- 更新当前下载量和总下载量
function UIAutoPatch:updateDownloadInfo(str)
    self.statusLabel:setString(str);
end

-- 显示"更新中"提示语
function UIAutoPatch:updateUpdatingLabel(isShow)
    -- 不满足条件就隐藏
    -- TODO ios版本暂时不显示更新中
    if not isShow then
        self.spUpdateTip:setVisible(false);
        return;
    end

    -- 如果已显示，那么return
    if self.spUpdateTip:isVisible() then
        return;
    end

    -- 更新中文本
    self.spUpdateTip:setVisible(true);
    self:setTextStyle(self.spUpdateTip);
    self.spUpdateTip:setString(getCoreLocStr("tip_updating"));
end

-- 进度条冈布奥跳动动画
function UIAutoPatch:progressBarEffect()
    self.pNode:setVisible(true);
    local node = self.barEffect;
    local height = 20;

    local scaleX = node:getScaleX();
    local scaleY = node:getScaleY();
    local posY = node:getPositionY();

    -- 向上部分
    local moveUp = cc.EaseIn:create(cc.MoveTo:create(0.15, cc.p(node:getPositionX(), height)), 0.25);

    -- 下降部分
    local moveDown = cc.EaseOut:create(cc.MoveTo:create(0.15, cc.p(node:getPositionX(), posY)), 0.25);

    -- 还原
    local compressDown2 = cc.EaseOut:create(cc.ScaleTo:create(0.1, scaleX, scaleY-0.2), 2.5);
    local stretchUp2 = cc.EaseOut:create(cc.ScaleTo:create(0.05, scaleX, scaleY), 2.5);

    -- 动画
    node:runAction(cc.RepeatForever:create(cc.Sequence:create(moveUp, moveDown, compressDown2, stretchUp2)));
end

-- 更新动画
function UIAutoPatch:playUpdateAnimation(percent)
    -- 冈布奥的跳跃的位置
    local posX = self.loadingBar1BG:getContentSize().width / 100 * percent + 30;
    self.pNode:setPositionX(-self.loadingBar1BG:getContentSize().width/2 + posX);
    self.pNode:setVisible(true);

    self.lastPercent = self.lastPercent or 3.3;
    if self.lastPercent < percent and self.lastPercent < 99 then
        for i = self.lastPercent, percent, 3.3 do
            self:grassEffect(self.lastPercent);
            self.lastPercent = self.lastPercent + 3.3;
        end
        self.lastPercent = self.lastPercent + 3.3;
    elseif percent + 3.3 > self.lastPercent then
        self:grassEffect(percent);
        self.lastPercent = self.lastPercent + 3.3;
    end

    if percent > 93 then
        self.pNode:setVisible(false);
    end
end

-- 草地动画
function UIAutoPatch:grassEffect(percent)
    local curNum = math.floor(percent / 17.1);
    local curIndex = math.floor(math.floor(percent / 3.3) % 5);

    if math.floor(percent / 3.3) == 0 then
        curIndex = 1;
    else
        curIndex = (curIndex == 0) and 5 or curIndex;
    end

    local offset = 524 / 6 * curNum + grassOffset[curIndex] + 7;

    local grassImg = ccui.ImageView:create();
    local name = "g0" .. curIndex .. ".png";
    grassImg:loadTexture("images/ui/login/effect/" .. name, 1);
    grassImg:setAnchorPoint(0.5, 0.1);
    grassImg:setScale(0.05);

    local posX = self.loadingBar1BG:getContentSize().width/100 + offset;
    if posX > self.loadingBar1BG:getContentSize().width - 20 then
        grassImg:setVisible(false);
        return;
    end

    grassImg:setPosition(self.loadingBar1BG:getContentSize().width/100 + offset, offsetY);
    self.loadingBar1BG:addChild(grassImg);

    local time = math.random(1, 4) / 10;
    local scaleUp = cc.ScaleTo:create(time, 1.2);
    local scaleDown = cc.ScaleTo:create(time/5, 1);

    local function grassFlowerEffect(x, y, name)
        local flowerImg = ccui.ImageView:create();
        flowerImg:loadTexture("images/ui/login/effect/" .. name .. ".png", 1);
        flowerImg:setScale(0);
        flowerImg:setPosition(x, y);
        grassImg:addChild(flowerImg);

        local callFunc = cc.CallFunc:create(function()
            local flowerScaleUp = cc.ScaleTo:create(0.2, 1.2);
            local flowerScaleDown = cc.ScaleTo:create(0.05, 1);
            flowerImg:runAction(cc.Sequence:create(flowerScaleUp, flowerScaleDown));
        end);

        grassImg:runAction(cc.Sequence:create(scaleUp, scaleDown, callFunc));
    end

    if curIndex == 3 then
        grassFlowerEffect(20, 45, "f02");
    else
        local prob = math.random(0, 100);
        if prob > flowerProb then
            fX = math.random(0, 40);
            fY = math.random(25, 35);
            grassFlowerEffect(fX, fY, "f01");
        else
            grassImg:runAction(cc.Sequence:create(scaleUp, scaleDown));
        end
    end
end

-- 更新完毕的动画
function UIAutoPatch:finishEffect()
    -- 检查下有没有子节点，没有的话用通用的草地资源
    local childs = self.loadingBar1BG:getChildren();
    if not childs or #childs == 0 then
        local grassImg = ccui.ImageView:create();
        grassImg:loadTexture("images/ui/login/effect/progress.png");
        grassImg:setAnchorPoint(0.5, 0);
        grassImg:setPosition(self.loadingBar1BG:getContentSize().width/2, 3);
        self.loadingBar1BG:addChild(grassImg);
    end

    -- 更新完毕播放下进度条动画
    local scaleX = self.loadingBar1BG:getScaleX();
    local scaleY = self.loadingBar1BG:getScaleY();
    local scaleDown1 = cc.ScaleTo:create(0.1, scaleX, scaleY - 0.2);
    local scaleUp1   = cc.ScaleTo:create(0.1, scaleX, scaleY + 0.2);
    local scaleDown2 = cc.ScaleTo:create(0.1, scaleX, scaleY);

    self.loadingBar1BG:runAction(cc.Sequence:create(scaleDown1, scaleUp1, scaleDown2));
end

-- 决定本地版本
function UIAutoPatch:initLocalVersion()
    -- 只初始化， 不做下载，为了获取本地版本号
    local am = cc.AssetsManagerEx:create(sceneManifests, cc.FileUtils:getInstance():getWritablePath() .. storagePaths, self.targetParts);

    if am then
        CLIENT_VERSION = am:getLocalManifest():getVersion();
    end

    if (CLIENT_VERSION == nil or CLIENT_VERSION == "") then
        CLIENT_VERSION = "1.0";
    end
end

-- 更新完毕，跳转到游戏界面
function UIAutoPatch:gotoMain()
    local function callback()
        AUTOPATCH_UI = nil;

        EventMgr.fire(event.AUTOPATCH_GOTO_MAIN);

        require "Main";
        if not checkImportantBoard() then
            UIMgr.startUI();
        else
            showImportantBoard();
        end
    end

    self:stopAllActions();
    performWithDelay(self, callback, 1);
end

-- 是否在白名单中
function isInWhiteList()
    local dislist = _G["dislist"];
    if type(dislist) ~= "table" then
        return false;
    end

    -- 是否开启白名单功能
    local enable_white_list = _G["dislist"]["enable_white_list"] or 0;
    if enable_white_list ~= 1 then
        return false;
    end

    -- 是否在白名单列表中
    local macAddress = NetworkStatus.getMacAddress();

    local whiteList = _G["dislist"]["white_list"] or {};
    for _, addr in pairs(whiteList) do
        if macAddress == addr then
            return true;
        end
    end

    return false;
end

--创建UI层
function createLayer()
    local layer  = UIAutoPatch.create();
    AUTOPATCH_UI = layer;
    return layer;
end

-- 创建场景
function createScene()
    local scene = cc.Scene:create();
    scene:addChild(createLayer());

    return scene;
end

-- 进入自动更新场景
function enterAutoPatchScene()
    local scene = createScene();
    if cc.Director:getInstance():getRunningScene() then
        cc.Director:getInstance():replaceScene(scene);
    else
        cc.Director:getInstance():runWithScene(scene);
    end
end

-- 开始自动更新
function start()
    -- initialize director
    local director = cc.Director:getInstance();

    if DEBUG_MODE == 1 or DEBUG_MODE == true then
        director:setDisplayStats(true);
        director:setAnimationInterval(1.0 / 60);
    else
        director:setAnimationInterval(1.0 / 30);
    end

    -- 设置屏幕常亮
    cc.Device:setKeepScreenOn(true);

    -- 布局不使用缩放，而是代码自行实现自适应布局
    local w = frameWidth;
    local h = frameHeight;
    cc.Director:getInstance():getOpenGLView():setDesignResolutionSize(w, h, 0);

    -- 进入自动更新场景
    enterAutoPatchScene();

    EventMgr.fire(event.AUTOPATCH_ENTER_SCENE);
end

-- 处理下区组列表
processDic = function()
    local content = _G["dislist"];
    if nil == content then return end

    ui.showVersionLabel = content["show_local_version"] or 0;
    ui:showLocalVersion();

    -- 获取访问地址
    local serverIp = content["server_ip"];
    local serverPort = content["server_port"];
    if (serverIp ~= nil and serverIp ~= "") and (serverPort ~= nil and serverPort ~= "") then
        SERVER_IP = serverIp;
        SERVER_PORT = serverPort;
    end

    -- 获取cc的ip和端口
    local ccIp = content["cc_ip"];
    ccIp = fixHostname(ccIp);

    local ccPort = content["cc_port"];
    if (ccIp ~= nil and ccIp ~= "") and (ccPort ~= nil and ccPort ~= "")  then
        CC_IP = ccIp;
        CC_PORT = ccPort;
    end

    -- 获取配置的测试平台
    local testPlatform = content["test_platform"];
    if testPlatform ~= nil and testPlatform ~= "" then
        TEST_PLATFORM = testPlatform;
    end

    -- 获取资源下载路径
    ui.dowloadRootUrl = content["download_root_url"];
    ui.dowloadRootUrl = fixUrl(ui.dowloadRootUrl);

    -- 获取下载包目标
    ui.targetParts = content["target_parts"];

    -- 测试资源下载
    ui.testPkgDownload = content["test_pkg_download"];

    -- 是否开放健康游戏忠告
    -- 本地记录一把，用于之后的处理
    local isForBh = content["is_for_bh"] or 0;
    UserDefaultM.setStringForKey("isForBh", tostring(isForBh));

    if content["upgrade_by_server_version"] == 0 then
        -- 维护完毕，清除服务器支持版本标记
        if UserDefaultM.getStringForKey("server_support_ver") ~= "0" then
            UserDefaultM.setStringForKey("server_support_ver", "0");
        end
    end
end

-- 是否可以显示outBoard
function canShowOutBoard(output)
    local lang = UserDefaultM.getStringForKey("APP_LANG");
    if lang == nil or lang == "" then
        -- 默认英文
        lang = "en";
    end

    local showLanguage = output["show_language"] or "*";
    if showLanguage ~= "*" then
        local langs = string.explode(showLanguage, "|");

        for _, canLang in pairs(langs) do
            if canLang == lang then
                -- 配置要显示
                return true;
            end
        end

        -- 不在限制的语言内，不显示
        return false;
    end

    -- 没配置，全显示
    return true;
end

-- 游戏一启动，就开始后台加载一些信息，比如公告、区组列表等
function UIAutoPatch:runTask()
    printG("1. 开始下载公告信息");
    auDownloadBoard(function(output)
        if false == output then
            _G["outBoard"] = nil;
        else
            if canShowOutBoard(output) then
                _G["outBoard"] = output;
            else
                _G["outBoard"] = nil;
            end
        end
        gotBoard = true;
        printG("公告信息下载完毕");
    end);

    -- 第三步：检查版本号
    local step3 = function(r)
        printG("3. 检查版本号是否一致");
        if false == r then
            printG("没有区组列表")
        else
            r = auCheckVersion();
        end
        if r == "=" then
            printG("不需要做任何更新，先把Main加载进来！");
            require "Main";
            if checkImportantBoard() then
                preloadMain();
            end
            return;
        end
    end

    printG("2. 同步下载区组列表信息");
    auLoadDislist(function(content)
        printG("区组列表下载完毕");
        gotDis = true;
        if content == false then
            _G["dislist"] = nil;
            step3(false);
            return;
        end

        local function downloadDislist2Finished(content2)
            if content2 ~= false then
                printG("dislist2下载成功，替换原来的dislist");
                _G["dislist"] = content2;
            end

            gotDis = true;
            processDic();
            step3(true);
        end

        _G["dislist"] = content;
        if type(_G["dislist"]["test_platform"]) == "string" and #_G["dislist"]["test_platform"] > 0 then
            printG("当前为测试平台，尝试下载正式平台区组信息");
            gotDis = false;
            auLoadDislist(downloadDislist2Finished, true);
        else
            processDic();
            step3(true);
        end
    end);

    if PUBLISH_PLATFORM == PUBLISH_TYPE_LT and isIosPlatform() then
        printG("3. 测试网络连通性");
        local function onTestFinish(ret)
            self.networkDisabled = not ret;
            gotNetwork = true;
        end
        auTestNetwork(self, onTestFinish);
    end
end

LAST_TRACKBACK = cc.UserDefault:getInstance():getStringForKey("track_back");
if LAST_TRACKBACK == nil then
    LAST_TRACKBACK = "";
end

function __G__TRACKBACK__(msg)
    -- 定义局部变量保存msg
    local message = msg;

    LAST_TRACKBACK = "";
    LAST_TRACKBACK = LAST_TRACKBACK .. "------------" .. os.date() .. "----------\n";
    LAST_TRACKBACK = LAST_TRACKBACK .. "LUA ERROR: " .. tostring(msg) .. "\n";
    LAST_TRACKBACK = LAST_TRACKBACK .. debug.traceback();
    LAST_TRACKBACK = LAST_TRACKBACK.. "\n----------------------------------------\n";

    recordTraceback(LAST_TRACKBACK)

    cc.UserDefault:getInstance():setStringForKey("track_back", LAST_TRACKBACK);

    if DEBUG_MODE ~= 1 and (isAndroidPlatform() or isIosPlatform()) then
        -- 添加Bugly的Lua异常上报
        buglyReportLuaException(message, debug.traceback());
    end

    if TestFightM and TestFightM.isInTest() then
        -- 测试功能打开的时候，有错误写到文件中去
        TestFightM.logError(LAST_TRACKBACK);
    end

    return msg
end

local status, msg = xpcall(start, __G__TRACKBACK__)
if not status then
    error(msg)
end
